<?php

    /**
     * Significant
     *
     * An url engine to parse,analyse and create significant url
     * it needs an urls.xml file in the config directory
     *
     * @package      Core
     * @subpackage   Url_Engine
     * 
     * @license      GNU Lesser General Public Licence see LICENCE-LGPL file or http://www.gnu.org/licenses/lgpl.html
     */
    class Core_Url_Engine_Significant implements Core_Url_Engine_Interface
    {

    ////////////////////////////////////////////////////////////////////////////
	//                                Variables                               //
	////////////////////////////////////////////////////////////////////////////

		/**
		 * Zend_Registry object
		 *
		 * @var Zend_Registry
		 */
		protected $_registry = null;

        /**
        * Data to create significant url
        *
        * @var array
        */
        protected $_dataCreateUrl = null;

        /**
        * Data to parse and anaylize significant url, and to determine action, module etc..
        *
        * @var array
        */
        protected $_dataParseUrl =  null;

	////////////////////////////////////////////////////////////////////////////
	//                                 Methods                                //
	////////////////////////////////////////////////////////////////////////////

		/**
		 * Constructor
		 */
		public function __construct()
		{
            $this->_registry = Zend_Registry::getInstance();
		}

		/**
		 * Destructor
		 */
		public function __destruct()
		{
			unset($this);
		}

        /**
         * Parse some url components
         *
         * @param        string      $scriptNamePath : /path/index.php
         * @param        string      $pathinfo : the path info part of the url (part between script name and query)
         * @param        array       $params : url parameters (query part e.g. $_REQUEST)
		 * @throws		 Core_ArgumentException
		 * @throws		 Core_ArgumentNullException
         * @return       Core_Url_Action
         */
        public function parse($scriptNamePath, $pathinfo, array $params)
        {
            if ($scriptNamePath === null) {
				throw new Core_ArgumentNullException('scriptNamePath is a null reference');
			}

            if (strlen(trim($scriptNamePath)) == 0) {
				throw new Core_ArgumentException('scriptNamePath is a zero-length string');
			}

            if ($pathinfo === null) {
				throw new Core_ArgumentNullException('pathinfo is a null reference');
			}

			$compil = false;
			$cacheparseurl = Core_IO_Path::combine(PROJECT_TEMP_URLS, '~parse~');
			$cachecreateurl = Core_IO_Path::combine(PROJECT_TEMP_URLS, '~create~');

			if (!Core_IO_File::exists($cacheparseurl) || !Core_IO_File::exists($cachecreateurl)) {
				$compil = true;
			} else {
				$cacheparseurltime = @filemtime($cacheparseurl);
				$cachecreateurltime = @filemtime($cachecreateurl);

                foreach ($this->_registry['modules'] as $module) {
                    $file = Core_IO_Path::combine($module, $this->_registry['config']->urlengine->significantFile);

                    if (Core_IO_File::exists($file)) {
                        if (@filemtime($file) > $cacheparseurltime || @filemtime($file) > $cachecreateurltime) {
                            $compil = true;
                            break;
                        }
                    }
                }
			}

			if ($compil) {
                Core_Url_Engine_Significant_Compiler::compile($this->_registry['config']->urlengine->significantFile);
            }

            $this->_dataParseUrl = eval('return '. Core_IO_File::readAll($cacheparseurl) .';');
            $this->_dataCreateUrl = eval('return '. Core_IO_File::readAll($cachecreateurl) .';');

            return $this->_parse($scriptNamePath, $pathinfo, $params);
        }

        /**
        * @param        string      $scriptNamePath : /path/index.php
        * @param        string      $pathinfo : the path info part of the url (part between script name and query)
        * @param        array       $params : url parameters (query part e.g. $_REQUEST)
        * @return		Core_Url_Action
        */
        protected function _parse($scriptNamePath, $pathinfo, array $params)
        {
            $action = null;
            $isDefault = false;
            $url = new Core_Url($scriptNamePath, $params, $pathinfo);

            foreach ($this->_dataParseUrl as $k => $infoparsing) {
                // first parameter indicates wether the current entry point is a default entry or not
                if ($k == 0) {
                    $isDefault = $infoparsing;
                    continue;
                }

                if (count($infoparsing) < 5) {

                    /* we get an array like
                        array(
                            0 => 'module',
                            1 => 'action',
                            2 => 'selector handler',
                            3 => array('secondary', 'actions')
                        )
                    */
                    $selector = new Core_Selector_Url($infoparsing[2]);
                    $class = $selector->getClassName();

                    require_once $selector->getPath();
                    $handler = new $class();

                    $url->setParam('module', $infoparsing[0]);

                    // if an action exists in the current URL and if it belongs to the secondary part,
                    // we leave it, otherwise we take the one defined in the configuration
                    if ($infoparsing[3] && isset($params['action'])) {
                        if (strpos($params['action'], ':') === false) {
                            $params['action'] = 'Index:' . $params['action'];
                        }

                        if (in_array($params['action'], $infoparsing[3])) {
                            // the action could have been overwritten by a previous iteration
                            $url->setParam('action', $params['action']);
                        } else {
                            $url->setParam('action', $infoparsing[1]);
                        }
                    } else {
                        $url->setParam('action', $infoparsing[1]);
                    }

                    // call handler
                    if ($action = $handler->parse($url)) {
                        break;
                    }
                } else {
                    /* we get an array like
                        array(
                            0 => 'module',
                            1 => 'action',
                            2 => 'regexp_pathinfo',
                            3 => array('annee','mois'), // tableau des valeurs dynamiques, classées par ordre croissant
                            4 => array(true, false), // tableau des valeurs escapes
                            5 => array('bla' => 'cequejeveux' ) // tableau des valeurs statiques
                            6 => false ou array('act','act'...) // autres actions secondaires autorisées
                        )
                    */
                    if (preg_match($infoparsing[2], $pathinfo, $matches)) {
                        if (!empty($infoparsing[0])) {
                            $params['module'] = $infoparsing[0];
                        }

                        // if an action exists in the current URL and if it belongs to the secondary part,
                        // we leave it, otherwise we take the one defined in the configuration
                        if ($infoparsing[6] && isset($params['action']) ) {
                            if (strpos($params['action'], ':') === false) {
                                $params['action'] = 'Index:' . $params['action'];
                            }

                            if (!in_array($params['action'], $infoparsing[6]) && $infoparsing[1] != '') {
                                $params['action'] = $infoparsing[1];
                            }
                        } else {
                            if (!empty($infoparsing[1])) {
                                $params['action']=$infoparsing[1];
                            }
                        }

                        // merge static parameters
                        if ($infoparsing[5]) {
                            $params = array_merge($params, $infoparsing[5]);
                        }

                        if (count($matches)) {
                            array_shift($matches);

                            foreach ($infoparsing[3] as $k => $name) {
                                if (isset($matches[$k])) {
                                    if ($infoparsing[4][$k]) {
                                        $params[$name] = Core_Url::unescape($matches[$k]);
                                    } else {
                                        $params[$name] = $matches[$k];
                                    }
                                }
                            }
                        }

                        $action = new Core_Url_Action($params);
                        break;
                    }
                }
            }

            if (!$action) {
                if ($isDefault && $pathinfo == '') {
                   // si on n'a pas trouvé de correspondance, mais que c'est l'entry point
                   // par defaut pour le type de request courant, alors on laisse passer..
                   $action = new Core_Url_Action($params);
                } else {
                   try {
                       $action = Core_Url::get($gJConfig->urlengine->noActionFound, array(), Core_Url::Core_UrlACTION);
                    } catch (Exception $e) {
                       $action = new Core_Url_Action(array('module' => 'core', 'action' => 'error:notFound'));
                   }
                }
            }

            return $action;
        }


        /**
         * Create a URL with the given action data
         *
         * @param		Core_UrlAction	$url : information about the action
         * @return		Core_Url		the url corresponding to the action
         */
        public function create(Core_Url_Action $url_action)
        {
            if ($this->_dataCreateUrl == null) {
                $compil = false;
                $cachecreateurl = Core_IO_Path::combine(PROJECT_TEMP_URLS, '~create~');

                if (!Core_IO_File::exists($cachecreateurl)) {
                    $compil = true;
                } else {
                    $cachecreateurltime = @filemtime($cachecreateurl);

                    foreach ($this->_registry['modules'] as $module) {
                        $file = Core_IO_Path::combine($module, $this->_registry['config']->urlengine->significantFile);

                        if (Core_IO_File::exists($file)) {
                            if (@filemtime($file) > $cachecreateurltime) {
                                $compil = true;
                                break;
                            }
                        }
                    }
                }

                if ($compil) {
                    Core_Url_Engine_Significant_Compiler::compile($this->_registry['config']->urlengine->significantFile);
                }

                $this->_dataCreateUrl = eval('return '. Core_IO_File::readAll($cachecreateurl) .';');
            }

            /*
                a) recupere module~action@request -> obtient les infos pour la creation de l'url
                b) récupère un à un les parametres indiqués dans params à partir de Core_Url
                c) remplace la valeur récupérée dans le result et supprime le paramètre de l'url
                d) remplace scriptname de Core_Url par le resultat
            */
            $url = new Core_Url('', $url_action->getParams(), '');

            $module = $url->getParam('module', Core_Context::get());
            $action = $url->getParam('action');

            $urlinfo = null;
            $id = $module . '~' . $action . '@' . $url_action->getRequestType();

            if (isset($this->_dataCreateUrl[$id])) {
                $urlinfo = $this->_dataCreateUrl[$id];

                $url->deleteParam('module');
                $url->deleteParam('action');
            } else {
                $id = $module . '~*@' . $url_action->getRequestType();

                if (isset($this->_dataCreateUrl[$id])) {
                    $urlinfo = $this->_dataCreateUrl[$id];
                    $url->deleteParam('module');
                } else {
                    $id = '@' . $url_action->getRequestType();
                    if (isset($this->_dataCreateUrl[$id])) {
                        $urlinfo = $this->_dataCreateUrl[$id];
                    } else {
                        throw new Exception("Significant url engine doesn't find corresponding url to this action: ". $module . '~' . $action . '@' . $url_action->getRequestType());
                    }
                }
            }

            /*
                urlinfo =
                    array(0, 'entrypoint', https true/false, 'selector handler')
                    or
                    array(1, 'entrypoint', https true/false,
                            array('annee', 'mois', 'jour', 'id', 'titre'), // liste des paramètres de l'url à prendre en compte
                            array(true, false), // valeur des escapes
                            "/news/%1/%2/%3/%4-%5", // forme de l'url
                            false, //indique si  c'est une action surchargeante
                            )
                    or
                    array(2, 'entrypoint', https true/false,); pour les clés du type "@request"
                    array(3, 'entrypoint', https true/false); pour les clés du type "module~@request"
                    array(4, array(1,..), array(1,..)...);
            */

            if ($urlinfo[0] == 4) {
                $l = count($urlinfo);
                $urlinfofound = null;

                for ($i = 1; $i < $l; $i++) {
                    $ok = true;

                    foreach ($urlinfo[$i][7] as $n => $v) {
                        if ($url->getParam($n, '') != $v) {
                            $ok = false;
                            break;
                        }
                    }

                    if ($ok) {
                        $urlinfofound = $urlinfo[$i];
                        break;
                    }
                }

                if ($urlinfofound !== null) {
                    $urlinfo = $urlinfofound;
                } else {
                    $urlinfo = $urlinfo[1];
                }
            }

            $base = '';
            if ($this->_registry['end'] == 'admin') {
                $base = trim($this->_registry['config']->urlengine->basePath, '/') . '/serum/';
            } else {
                $base = trim($this->_registry['config']->urlengine->basePath, '/') . '/';
            }

			if (substr($base, 0, 1) != '/') {
				$base = '/' . $base;
			}

            $url->setScriptName('http://'. $_SERVER['HTTP_HOST'] . $base . $urlinfo[1]);

            if ($urlinfo[2]) {
                $url->setScriptName('https://'. $_SERVER['HTTP_HOST'] . $base . $urlinfo[1]);
            }

            if ($urlinfo[1] && !$this->_registry['config']->urlengine->multiview) {
                $url->setScriptName($url->getScriptName() . $this->_registry['config']->urlengine->entrypointExtension);
            }

            // pour certains types de requete, les paramètres ne sont pas dans l'url
            // donc on les supprime
            // c'est un peu crade de faire ça en dur ici, mais ce serait lourdingue
            // de charger la classe request pour savoir si on peut supprimer ou pas
            if (in_array($url_action->getRequestType(), array('xml', 'json', 'soap'))) {
                $url->clearParam();
                return $url;
            }

            if ($urlinfo[0] == 0) {
                $selector = new Core_Selector_Url($urlinfo[3]);
                $class = $selector->getClass();

                require_once $selector->getPath();
                $handler = new $class();

                $handler->create($url_action, $url);
            } else if ($urlinfo[0] == 1) {
                $pi = $urlinfo[5];

                foreach ($urlinfo[3] as $k => $param) {
                    if ($urlinfo[4][$k]) {
                        $pi = str_replace(':'. $param, Core_Url::escape($url->getParam($param, ''), true), $pi);
                    } else {
                        $pi = str_replace(':'. $param, $url->getParam($param, ''), $pi);
                    }

                    $url->deleteParam($param);
                }

                $url->setPathInfo($pi);

                if ($urlinfo[6]) {
                    $url->setParam('action', $action);
                }

                // removed parameters corresponding to static values
                foreach ($urlinfo[7] as $name => $value) {
                    $url->deleteParam($name);
                }
            } else if ($urlinfo[0] == 3) {
                $url->deleteParam('module');
            }

            return $url;
        }
    }
